using SeataNet.Core.Common;
using SeataNet.Core.Exceptions;
using SeataNet.Core.Model;
using SeataNet.Core.RPC;
using Microsoft.Extensions.Logging;
using System.Collections.Concurrent;
using System.Linq;

namespace SeataNet.Core.Transaction
{
  /// <summary>
  /// 默认资源管理器实现
  ///
  /// 职责：
  /// 1. 管理分支事务的注册、提交、回滚和状态上报
  /// 2. 管理资源（数据库连接、数据源等）的注册和注销
  /// 3. 提供全局锁查询功能
  /// 4. 作为RM（Resource Manager）与TC（Transaction Coordinator）通信
  ///
  /// 工作流程：
  /// 1. 资源注册：应用启动时注册数据源到资源管理器
  /// 2. 分支注册：执行SQL前向TC注册分支，获取分支ID和锁
  /// 3. 分支执行：执行本地SQL操作，生成undo_log
  /// 4. 状态上报：执行完成后向TC上报分支状态
  /// 5. 二阶段操作：
  ///    - 提交：删除undo_log，释放锁
  ///    - 回滚：根据undo_log生成反向SQL并执行，释放锁
  ///
  /// 架构角色：
  /// - 在Seata架构中，ResourceManager是RM的实现
  /// - RM负责管理分支事务的本地资源
  /// - RM与TC通过RPC通信
  /// - RM与TM（Transaction Manager）协同工作
  ///
  /// 线程安全：
  /// - 使用ConcurrentDictionary存储资源，支持并发访问
  /// - RPC客户端是线程安全的
  /// - 分支操作都是异步的，避免阻塞
  ///
  /// 注意事项：
  /// - 资源ID必须唯一，通常使用数据库连接串
  /// - 分支注册必须在SQL执行前完成
  /// - 提交和回滚操作可能异步执行，不保证立即完成
  /// </summary>
  public class DefaultResourceManager(IRpcClient rpcClient, ILogger<DefaultResourceManager> logger) : IResourceManager
  {
    private readonly IRpcClient _rpcClient = rpcClient ?? throw new ArgumentNullException(nameof(rpcClient));
    private readonly ILogger<DefaultResourceManager> _logger = logger ?? throw new ArgumentNullException(nameof(logger));
    // 资源字典，key为ResourceId，value为IResource实例
    // 使用ConcurrentDictionary保证线程安全
    private readonly ConcurrentDictionary<string, IResource> _resources = new();

    /// <summary>
    /// 注册分支事务
    ///
    /// 向TC注册一个分支事务，获取分支ID和全局锁。
    ///
    /// 执行流程：
    /// 1. 构造分支注册请求，包含XID、分支类型、资源ID、锁键等
    /// 2. 通过RPC发送请求到TC
    /// 3. TC分配分支ID并检查锁冲突
    /// 4. 如果无冲突，TC返回分支ID
    /// 5. 如果有冲突，TC返回错误（抛出异常）
    /// 6. 记录注册成功日志
    ///
    /// 调用时机：
    /// - 在执行SQL之前必须先注册分支
    /// - 通常由SQL拦截器自动调用
    /// - 确保在全局事务上下文中（XID存在）
    ///
    /// 锁机制：
    /// - AT模式：TC根据lockKey锁定相关数据行
    /// - 如果其他全局事务已锁定，当前注册会失败
    /// - 锁会在第二阶段（提交或回滚）释放
    ///
    /// 异常处理：
    /// - 锁冲突：TransactionException，需要重试或回滚
    /// - TC不可达：TransactionException，网络问题
    /// - 无效响应：TransactionException，branchId解析失败
    /// </summary>
    /// <param name="branchType">分支事务类型（AT/TCC/Saga/XA）</param>
    /// <param name="xid">全局事务ID</param>
    /// <param name="resourceId">资源ID，标识数据库或数据源</param>
    /// <param name="lockKey">锁键，格式为"表名:主键值"，可选</param>
    /// <param name="applicationData">应用自定义数据，可选</param>
    /// <returns>TC分配的分支事务ID</returns>
    /// <exception cref="TransactionException">注册失败时抛出</exception>
    public async Task<long> BranchRegisterAsync(string branchType, string xid, string resourceId, string? lockKey = null, string? applicationData = null)
    {
      try
      {
        // 创建分支注册请求
        var request = new
        {
          Xid = xid,
          BranchType = branchType,
          ResourceId = resourceId,
          LockKeys = lockKey,
          ApplicationData = applicationData
        };

        // 发送请求到TC
        var response = await _rpcClient.SendAsync<object, dynamic>(request, TransactionConstants.DEFAULT_GLOBAL_TIMEOUT);

        // 解析响应
        var branchId = response?.branchId?.ToString();

        long result = 0;
        if (string.IsNullOrEmpty(branchId) || !long.TryParse(branchId, out result))
        {
          throw new TransactionException("Failed to register branch: invalid branch id");
        }

        _logger.LogInformation("Register branch [{branchId}] for global transaction [{xid}]", result, xid);

        return result;
      }
      catch (Exception ex)
      {
        _logger.LogError(ex, "Failed to register branch for global transaction [{xid}]", xid);
        throw new TransactionException($"Failed to register branch for global transaction {xid}", ex);
      }
    }

    /// <summary>
    /// 报告分支事务状态
    ///
    /// 向TC上报分支事务的执行状态。
    ///
    /// 执行流程：
    /// 1. 构造分支报告请求，包含XID、分支ID、状态等
    /// 2. 通过RPC发送请求到TC
    /// 3. TC记录该分支的状态
    /// 4. TC根据所有分支状态决定全局事务的下一步操作
    /// 5. 返回上报结果
    ///
    /// 状态类型说明：
    /// - PhaseOneDone: 第一阶段完成（本地事务提交成功，undo_log已生成）
    /// - PhaseOneFailed: 第一阶段失败（本地事务执行失败）
    /// - PhaseTwoCommitted: 第二阶段提交完成（undo_log已删除）
    /// - PhaseTwoRollbacked: 第二阶段回滚完成（已根据undo_log恢复数据）
    ///
    /// 调用时机：
    /// - 第一阶段：本地事务执行完成后（成功或失败）
    /// - 第二阶段：收到TC的提交或回滚指令并执行完成后
    ///
    /// 注意事项：
    /// - 状态上报失败会抛出异常
    /// - TC依赖状态上报来决定全局事务的最终结果
    /// - 建议实现重试机制以确保状态最终到达TC
    /// </summary>
    /// <param name="xid">全局事务ID</param>
    /// <param name="branchId">分支事务ID</param>
    /// <param name="status">分支事务状态</param>
    /// <param name="applicationData">应用自定义数据，可选</param>
    /// <returns>true表示上报成功，false表示上报失败</returns>
    /// <exception cref="TransactionException">上报失败时抛出</exception>
    public async Task<bool> BranchReportAsync(string xid, long branchId, BranchStatus status, string? applicationData = null)
    {
      try
      {
        // 创建分支报告请求
        var request = new
        {
          Xid = xid,
          BranchId = branchId,
          Status = (int)status,
          ApplicationData = applicationData
        };

        // 发送请求到TC
        var response = await _rpcClient.SendAsync<object, dynamic>(request, TransactionConstants.DEFAULT_GLOBAL_TIMEOUT);

        // 解析响应
        var result = response?.result?.ToString();

        bool success = "true".Equals(result, StringComparison.OrdinalIgnoreCase);

        _logger.LogInformation("Report branch [{branchId}] for global transaction [{xid}], status: [{status}], result: [{result}]",
            branchId, xid, status, success ? "success" : "failed");

        return success;
      }
      catch (Exception ex)
      {
        _logger.LogError(ex, "Failed to report branch [{branchId}] for global transaction [{xid}]", branchId, xid);
        throw new TransactionException($"Failed to report branch {branchId} for global transaction {xid}", ex);
      }
    }

    /// <summary>
    /// 查询分支事务锁
    ///
    /// 查询指定的数据行是否被其他全局事务锁定。
    ///
    /// 执行流程：
    /// 1. 构造锁查询请求，包含XID和锁键集合
    /// 2. 将锁键集合用逗号连接成字符串
    /// 3. 通过RPC发送请求到TC
    /// 4. TC检查这些锁键是否被其他全局事务持有
    /// 5. 返回锁定状态
    ///
    /// 使用场景：
    /// - 在分支注册前，预先检查是否存在锁冲突
    /// - 实现乐观锁重试机制
    /// - 避免不必要的分支注册尝试
    ///
    /// 锁检查逻辑：
    /// - 如果lockKeys中的任何一个被其他XID锁定，返回false（不可锁定）
    /// - 如果lockKeys都未被锁定，或被当前XID锁定，返回true（可锁定）
    /// - 同一个XID可以重复锁定相同的行
    ///
    /// 注意事项：
    /// - 查询结果只代表查询时刻的状态，不保证后续注册一定成功
    /// - 查询和注册之间可能有其他事务获得锁
    /// - 建议查询后立即注册，减小时间窗口
    /// </summary>
    /// <param name="xid">全局事务ID</param>
    /// <param name="lockKeys">锁键集合，每个锁键格式为"表名:主键值"</param>
    /// <returns>true表示可以锁定（无冲突），false表示不可锁定（有冲突）</returns>
    /// <exception cref="TransactionException">查询失败时抛出</exception>
    public async Task<bool> LockQueryAsync(string xid, ICollection<string> lockKeys)
    {
      try
      {
        // 创建锁查询请求
        var request = new
        {
          Xid = xid,
          LockKeys = string.Join(",", lockKeys)
        };

        // 发送请求到TC
        var response = await _rpcClient.SendAsync<object, dynamic>(request, TransactionConstants.DEFAULT_GLOBAL_TIMEOUT);

        // 解析响应
        var result = response?.lockable?.ToString();

        bool lockable = "true".Equals(result, StringComparison.OrdinalIgnoreCase);

        _logger.LogInformation("Query lock for global transaction [{xid}], lockable: [{lockable}]",
            xid, lockable);

        return lockable;
      }
      catch (Exception ex)
      {
        _logger.LogError(ex, "Failed to query lock for global transaction [{xid}]", xid);
        throw new TransactionException($"Failed to query lock for global transaction {xid}", ex);
      }
    }

    /// <summary>
    /// 提交分支事务
    ///
    /// 执行分支事务的第二阶段提交操作（删除undo_log并释放锁）。
    ///
    /// 执行流程：
    /// 1. 构造分支提交请求，包含XID、分支ID、分支类型等
    /// 2. 通过RPC发送请求到TC
    /// 3. TC通知RM执行第二阶段提交
    /// 4. RM执行以下操作：
    ///    - 删除undo_log表中对应的记录
    ///    - 释放TC中的全局锁
    ///    - 清理分支相关的资源
    /// 5. 返回提交结果
    ///
    /// AT模式的两阶段提交：
    /// - 第一阶段：执行SQL + 生成undo_log + 本地提交
    /// - 第二阶段（此方法）：删除undo_log + 释放锁
    ///
    /// 调用时机：
    /// - TC决定全局提交时，异步通知所有分支执行提交
    /// - 通常由TC主动调用，而非应用代码直接调用
    /// - 在GlobalCommit之后由TC协调执行
    ///
    /// 注意事项：
    /// - 第二阶段提交通常是异步的，不会阻塞GlobalCommit返回
    /// - 提交失败需要重试，直到成功为止
    /// - undo_log删除失败不影响数据一致性（数据已正确提交）
    /// - 提交操作应该是幂等的（重复提交不会有副作用）
    /// </summary>
    /// <param name="branchType">分支事务类型（AT/TCC/Saga/XA）</param>
    /// <param name="xid">全局事务ID</param>
    /// <param name="branchId">分支事务ID</param>
    /// <param name="resourceId">资源ID</param>
    /// <param name="applicationData">应用自定义数据，可选</param>
    /// <returns>true表示提交成功，false表示提交失败</returns>
    /// <exception cref="TransactionException">提交失败时抛出</exception>
    public async Task<bool> BranchCommitAsync(string branchType, string xid, long branchId, string resourceId, string? applicationData = null)
    {
      try
      {
        // 创建分支提交请求
        var request = new
        {
          Xid = xid,
          BranchId = branchId,
          BranchType = branchType,
          ResourceId = resourceId,
          ApplicationData = applicationData
        };

        // 发送请求到TC
        var response = await _rpcClient.SendAsync<object, dynamic>(request, TransactionConstants.DEFAULT_GLOBAL_TIMEOUT);

        // 解析响应
        var result = response?.result?.ToString();

        bool success = "true".Equals(result, StringComparison.OrdinalIgnoreCase);

        _logger.LogInformation("Commit branch [{branchId}] for global transaction [{xid}], result: [{result}]",
            branchId, xid, success ? "success" : "failed");

        return success;
      }
      catch (Exception ex)
      {
        _logger.LogError(ex, "Failed to commit branch [{branchId}] for global transaction [{xid}]", branchId, xid);
        throw new TransactionException($"Failed to commit branch {branchId} for global transaction {xid}", ex);
      }
    }

    /// <summary>
    /// 回滚分支事务
    ///
    /// 执行分支事务的第二阶段回滚操作（根据undo_log恢复数据并释放锁）。
    ///
    /// 执行流程：
    /// 1. 构造分支回滚请求，包含XID、分支ID、分支类型等
    /// 2. 通过RPC发送请求到TC
    /// 3. TC通知RM执行第二阶段回滚
    /// 4. RM执行以下操作（AT模式）：
    ///    - 从undo_log表查询对应的记录
    ///    - 解析undo_log，生成反向SQL
    ///    - 执行反向SQL，将数据恢复到事务前状态
    ///    - 删除undo_log记录
    ///    - 释放TC中的全局锁
    /// 5. 返回回滚结果
    ///
    /// AT模式的两阶段回滚：
    /// - 第一阶段：执行SQL + 生成undo_log + 本地提交
    /// - 第二阶段（此方法）：查询undo_log + 生成反向SQL + 执行 + 删除undo_log + 释放锁
    ///
    /// 调用时机：
    /// - TC决定全局回滚时，异步通知所有分支执行回滚
    /// - 业务异常、超时或手动回滚时触发
    /// - 通常由TC主动调用，而非应用代码直接调用
    ///
    /// 注意事项：
    /// - 回滚操作必须成功，否则需要人工介入
    /// - 回滚失败会进入事务恢复流程，定期重试
    /// - 数据可能被其他事务修改，需要版本检查（脏写检测）
    /// - 回滚操作应该是幂等的（重复回滚不会有副作用）
    /// - 如果undo_log不存在，可能是已经回滚过或数据损坏
    /// </summary>
    /// <param name="branchType">分支事务类型（AT/TCC/Saga/XA）</param>
    /// <param name="xid">全局事务ID</param>
    /// <param name="branchId">分支事务ID</param>
    /// <param name="resourceId">资源ID</param>
    /// <param name="applicationData">应用自定义数据，可选</param>
    /// <returns>true表示回滚成功，false表示回滚失败</returns>
    /// <exception cref="TransactionException">回滚失败时抛出</exception>
    public async Task<bool> BranchRollbackAsync(string branchType, string xid, long branchId, string resourceId, string? applicationData = null)
    {
      try
      {
        // 创建分支回滚请求
        var request = new
        {
          Xid = xid,
          BranchId = branchId,
          BranchType = branchType,
          ResourceId = resourceId,
          ApplicationData = applicationData
        };

        // 发送请求到TC
        var response = await _rpcClient.SendAsync<object, dynamic>(request, TransactionConstants.DEFAULT_GLOBAL_TIMEOUT);

        // 解析响应
        var result = response?.result?.ToString();

        bool success = "true".Equals(result, StringComparison.OrdinalIgnoreCase);

        _logger.LogInformation("Rollback branch [{branchId}] for global transaction [{xid}], result: [{result}]",
            branchId, xid, success ? "success" : "failed");

        return success;
      }
      catch (Exception ex)
      {
        _logger.LogError(ex, "Failed to rollback branch [{branchId}] for global transaction [{xid}]", branchId, xid);
        throw new TransactionException($"Failed to rollback branch {branchId} for global transaction {xid}", ex);
      }
    }

    /// <summary>
    /// 注册资源
    ///
    /// 将一个资源（数据库、数据源等）注册到资源管理器。
    ///
    /// 执行流程：
    /// 1. 检查资源对象不为null
    /// 2. 使用ResourceId作为key，将资源添加到或更新到字典中
    /// 3. 如果key已存在，会覆盖旧值
    /// 4. 记录注册日志
    ///
    /// 使用场景：
    /// - 应用启动时注册所有数据源
    /// - 动态添加数据源时注册
    /// - 连接池初始化后注册
    ///
    /// 资源类型：
    /// - 数据库连接池（IDbConnection）
    /// - 数据源（IDataSource）
    /// - 消息队列连接
    /// - 其他需要事务管理的资源
    ///
    /// 注意事项：
    /// - ResourceId必须唯一，通常使用连接串或数据源名称
    /// - 注册后才能进行分支事务操作
    /// - 同一个ResourceId重复注册会覆盖之前的资源
    /// - 线程安全：使用ConcurrentDictionary，支持并发注册
    /// </summary>
    /// <param name="resource">资源对象，实现IResource接口</param>
    /// <exception cref="ArgumentNullException">resource为null时抛出</exception>
    public void RegisterResource(IResource resource)
    {
      ArgumentNullException.ThrowIfNull(resource);

      _ = _resources.AddOrUpdate(resource.ResourceId, resource, (key, oldValue) => resource);

      _logger.LogInformation("Register resource [{resourceId}] of type [{resourceType}]",
          resource.ResourceId, resource.ResourceType);
    }

    /// <summary>
    /// 注销资源
    ///
    /// 从资源管理器中移除指定的资源。
    ///
    /// 执行流程：
    /// 1. 检查resourceId不为空
    /// 2. 从字典中移除对应的资源
    /// 3. 如果resourceId不存在，操作无效（不抛出异常）
    /// 4. 记录注销日志
    ///
    /// 使用场景：
    /// - 应用关闭时注销所有资源
    /// - 动态移除数据源
    /// - 连接池销毁前注销
    ///
    /// 注意事项：
    /// - 注销资源不会中断正在进行的事务
    /// - 注销后该资源无法再进行新的分支注册
    /// - 线程安全：使用ConcurrentDictionary，支持并发注销
    /// - 建议在没有活跃事务时注销资源
    /// </summary>
    /// <param name="resourceId">资源ID</param>
    /// <exception cref="ArgumentNullException">resourceId为空时抛出</exception>
    public void UnregisterResource(string resourceId)
    {
      if (string.IsNullOrEmpty(resourceId))
      {
        throw new ArgumentNullException(nameof(resourceId));
      }

      _ = _resources.TryRemove(resourceId, out _);

      _logger.LogInformation("Unregister resource [{resourceId}]", resourceId);
    }

    /// <summary>
    /// 获取资源
    ///
    /// 根据ResourceId查询已注册的资源。
    ///
    /// 使用场景：
    /// - 分支事务执行前查找对应的资源
    /// - 检查资源是否已注册
    /// - 获取资源进行配置或操作
    ///
    /// 线程安全：
    /// - 读取操作是线程安全的
    /// - 支持并发查询
    /// </summary>
    /// <param name="resourceId">资源ID</param>
    /// <returns>资源对象，如果未找到则返回null</returns>
    public IResource? GetResource(string resourceId)
    {
      _ = _resources.TryGetValue(resourceId, out IResource? resource);
      return resource;
    }

    /// <summary>
    /// 获取所有资源
    ///
    /// 返回资源管理器中所有已注册的资源集合。
    ///
    /// 使用场景：
    /// - 监控和诊断：查看所有注册的资源
    /// - 管理界面：显示资源列表
    /// - 批量操作：对所有资源执行某些操作
    /// - 健康检查：检查所有资源的连接状态
    ///
    /// 注意事项：
    /// - 返回的是集合的快照，不会随后续注册/注销而变化
    /// - 集合是只读的，修改不会影响原字典
    /// - 线程安全：读取操作是线程安全的
    /// </summary>
    /// <returns>所有资源的集合</returns>
    public ICollection<IResource> GetAllResources()
    {
      return [.. _resources.Values];
    }

    /// <summary>
    /// 注销资源（IResource对象重载）
    ///
    /// 根据资源对象注销资源，实际调用UnregisterResource(resourceId)。
    ///
    /// 便捷方法：无需手动提取ResourceId，直接传入资源对象即可注销。
    /// </summary>
    /// <param name="resource">资源对象</param>
    /// <exception cref="ArgumentNullException">resource为null时抛出</exception>
    public void UnregisterResource(IResource resource)
    {
      ArgumentNullException.ThrowIfNull(resource);
      _ = _resources.TryRemove(resource.ResourceId, out _);

      _logger.LogInformation("Unregister resource [{resourceId}]", resource.ResourceId);
    }

    /// <summary>
    /// 报告分支事务状态（int类型状态值重载）
    ///
    /// BranchReportAsync(BranchStatus)的便捷重载版本。
    ///
    /// 使用场景：
    /// - 当状态值以int类型传递时使用
    /// - 与其他系统集成时使用数字状态码
    ///
    /// 实现方式：
    /// - 将int状态码转换为BranchStatus枚举
    /// - 调用主实现方法
    /// </summary>
    /// <param name="xid">全局事务ID</param>
    /// <param name="branchId">分支事务ID</param>
    /// <param name="status">分支事务状态（int类型）</param>
    /// <param name="applicationData">应用自定义数据，可选</param>
    /// <returns>true表示上报成功，false表示上报失败</returns>
    public Task<bool> BranchReportAsync(string xid, long branchId, int status, string? applicationData = null)
    {
      // 将int状态转换为BranchStatus枚举,然后调用主实现
      return BranchReportAsync(xid, branchId, (BranchStatus)status, applicationData);
    }
  }
}